﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;
using XNADota.Primitive;
using System.Diagnostics;
using XNADota.Graphics;
using XNADota.MDX;

namespace XNADota.Terrain
{
    // basic terrain cell
    public class LandscapePatchRenderer : PaletteRenderer
    {
        public const int DefaultResolution = 16;
        public const int DefaultGridSize = 128;

        // Represent how many grids one terrain cell edge has
        private int _Resolution;
        private BoundingBox _BoundingBox;
        private Vector3 _Position;

        private VertexDeclaration _VertexDeclaration;
        private VertexBuffer _VertexBuffer;
        private IndexBuffer _IndexBuffer;
        private BasicEffect _BasicEffect;

        private ERenderState _RenderStates;

        public Vector3 Position
        {
            get { return _Position; }
        }

        public BoundingBox BoundingBox
        {
            get { return _BoundingBox; }
        }

        public int Resolution
        {
            get { return _Resolution; }
        }

        public LandscapePatchRenderer(Game game, GraphicsDevice GraphicsDevice, Landscape Landscape, int Resolution,
            VertexPositionNormalTangentBinormalTexture[] Vertices, VertexInfo[,] VertexInfos)
        {
            if (Vertices.Length != (Resolution + 1) * (Resolution + 1))
                throw new InvalidTerrainException("Failed in generating landscape cell.");

            _Resolution = Resolution;
            int Exponent = (int)Math.Log(_Resolution, 2);

            if (Math.Pow(2, Exponent) != _Resolution)
                throw new InvalidTerrainException("The landscape cell size is not power of two.");

            // Declaration
            _VertexDeclaration = new VertexDeclaration(GraphicsDevice,
                                    VertexPositionNormalTangentBinormalTexture.VertexElements);

            this._VertexBuffer = new VertexBuffer(GraphicsDevice, typeof(VertexPositionNormalTangentBinormalTexture), Vertices.Length, BufferUsage.None);
            this._VertexBuffer.SetData(Vertices);

            int[] indices = GenerateIndexBuffer(game, Resolution, Resolution, VertexInfos);
            this._IndexBuffer = new IndexBuffer(GraphicsDevice, typeof(int), indices.Length, BufferUsage.None);
            this._IndexBuffer.SetData(indices);

            // Create a BasicEffect, which will be used to render the primitive.
            _BasicEffect = new BasicEffect(GraphicsDevice, null);

            _BasicEffect.TextureEnabled = true;
            _BasicEffect.Texture = GenerateTiledTexture(GraphicsDevice, Landscape, Resolution, Resolution, VertexInfos);//Landscape.TextureInfo[0].TerrainTexture as Texture2D;

            _RenderStates = ERenderState.Opaque | ERenderState.EnableDepth | ERenderState.NoCull | ERenderState.Solid;


            // Generate BoundingBox and Position
            _Position = (VertexInfos[0, 0].Position + VertexInfos[_Resolution, _Resolution].Position) / 2;

            Vector3 Min = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue);
            Vector3 Max = new Vector3(float.MinValue, float.MinValue, float.MinValue);
            for (int i = 0; i < _Resolution; i++)
                for (int j = 0; j < _Resolution; j++ )
                {
                    Min.X = Math.Min(VertexInfos[i, j].Position.X, Min.X);
                    Max.X = Math.Max(VertexInfos[i, j].Position.X, Max.X);

                    Min.Y = Math.Min(VertexInfos[i, j].Position.Y, Min.Y);
                    Max.Y = Math.Max(VertexInfos[i, j].Position.Y, Max.Y);

                    Min.Z = Math.Min(VertexInfos[i, j].Position.Z, Min.Z);
                    Max.Z = Math.Max(VertexInfos[i, j].Position.Z, Max.Z);
                }

            Vector3 Extent = new Vector3(DefaultGridSize, DefaultGridSize, DefaultGridSize);

            _BoundingBox = new BoundingBox(Min - Extent, Max + Extent);
        }

        private int Index(int x, int y, int width)
        {
            return x + y * width;
        }

        private int[] GenerateIndexBuffer(Game game, int width, int height, VertexInfo[,] VertexInfos)
        {
            Debug.Assert(VertexInfos.GetLength(0) == width + 1);
            Debug.Assert(VertexInfos.GetLength(1) == height + 1);

            // (i + 1, j)--> ( i + 1, j + 1)
            //    |             |
            // (i, j)    --> (i , j + 1)
            // 0 -> 1 -> 3, 0 -> 3 -> 2
            List<int> Triangles = new List<int>();
            for (int i = 0; i < width; i++)
                for (int j = 0; j < height; j++)
                {
                    string name;
                    ECellType cellType = CliffPaletteRendererContainer.GetCellType(
                        VertexInfos[i, j],
                        VertexInfos[i + 1, j],
                        VertexInfos[i + 1, j + 1],
                        VertexInfos[i, j + 1],
                        out name);

                    if (cellType == ECellType.Surface || cellType == ECellType.Ramp)
                    {
                        // 0 -> 1 ->3
                        Triangles.Add(Index(i, j, width + 1));
                        Triangles.Add(Index(i + 1, j, width + 1));
                        Triangles.Add(Index(i + 1, j + 1, width + 1));

                        // 0 -> 3 ->2
                        Triangles.Add(Index(i, j, width + 1));
                        Triangles.Add(Index(i + 1, j + 1, width + 1));
                        Triangles.Add(Index(i, j + 1, width + 1));
                    }

                    if (cellType == ECellType.SurfaceInvisible)
                    {
                        //DebugPrimitiveRenderer.AddPermanentPrimitive(VertexInfos[i, j].Position + new Vector3(0, 100, 0), Color.PowderBlue);
                        //DebugPrimitiveRenderer.AddPermanentPrimitive(VertexInfos[i + 1, j].Position + new Vector3(0, 100, 0), Color.PowderBlue);
                        //DebugPrimitiveRenderer.AddPermanentPrimitive(VertexInfos[i, j + 1].Position + new Vector3(0, 100, 0), Color.PowderBlue);
                        //DebugPrimitiveRenderer.AddPermanentPrimitive(VertexInfos[i + 1, j + 1].Position + new Vector3(0, 100, 0), Color.PowderBlue);

                        //VertexInfos[i, j].PrintDebugInfo();
                        //VertexInfos[i + 1, j].PrintDebugInfo();
                        //VertexInfos[i, j + 1].PrintDebugInfo();
                        //VertexInfos[i + 1, j + 1].PrintDebugInfo();
                    }
                }

            return Triangles.ToArray();
        }

        private Texture2D GenerateTiledTexture(GraphicsDevice GraphicsDevice, Landscape Landscape, int width, int height, VertexInfo[,] VertexInfos)
        {
            Debug.Assert(VertexInfos.GetLength(0) == width + 1);
            Debug.Assert(VertexInfos.GetLength(1) == height + 1);
            Debug.Assert(Landscape.TexturePallettes.Length > 0);

            if (VertexInfos.GetLength(0) != width + 1 || VertexInfos.GetLength(1) != height + 1)
                throw new Exception("VertexInfos size does not match desired width or height");

            if (Landscape.TexturePallettes.Length == 0)
                throw new InvalidTerrainException("Terrain palette has no elements.");

            const int cellSize = TexturePalette.CellSize;

            DepthStencilBuffer prevDepth = GraphicsDevice.DepthStencilBuffer;

            // Prepare 
            RenderTarget2D TargetSurface = new RenderTarget2D(GraphicsDevice, width * cellSize, height * cellSize, 1, SurfaceFormat.Color, RenderTargetUsage.PreserveContents);
            GraphicsDevice.SetRenderTarget(0, TargetSurface);
            GraphicsDevice.DepthStencilBuffer = new DepthStencilBuffer(GraphicsDevice, width * cellSize, height * cellSize, DepthFormat.Depth24Stencil8);
            GraphicsDevice.Clear(Color.White);

            SpriteBatch spriteBatch = new SpriteBatch(GraphicsDevice);
            int paletteCount = Landscape.TexturePallettes.Length;

            // set to immediate, let render state work
            spriteBatch.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.Deferred, SaveStateMode.SaveState);

            for (int i = 0; i < width; i++)
                for (int j = 0; j < height; j++)
                {
                    TexturePalette[] palettes = Landscape.TexturePallettes;
                    Rectangle destinationRect = new Rectangle(i * cellSize, j * cellSize, cellSize, cellSize);
                    int type = 0;

                    // valid ranges
                    Debug.Assert((0 <= VertexInfos[i, j].TextureId) && (VertexInfos[i, j].TextureId < paletteCount));
                    Debug.Assert((0 <= VertexInfos[i + 1, j].TextureId) && (VertexInfos[i + 1, j].TextureId < paletteCount));
                    Debug.Assert((0 <= VertexInfos[i, j + 1].TextureId) && (VertexInfos[i, j + 1].TextureId < paletteCount));
                    Debug.Assert((0 <= VertexInfos[i + 1, j + 1].TextureId) && (VertexInfos[i + 1, j + 1].TextureId < paletteCount));

                    // draw basic background, using the minimum index
                    int TexturePaletteIndex = Math.Min(Math.Min(VertexInfos[i, j].TextureId, VertexInfos[i, j + 1].TextureId),
                                                       Math.Min(VertexInfos[i + 1, j].TextureId, VertexInfos[i + 1, j + 1].TextureId));
                    Rectangle sourceRect = palettes[TexturePaletteIndex].GetRectangle(15);
                    spriteBatch.Draw(palettes[0].Texture as Texture2D, destinationRect, sourceRect, Color.White);

                    // draw more layers
                    for (TexturePaletteIndex = 0; TexturePaletteIndex < paletteCount; TexturePaletteIndex++)
                    {
                        // get the rendering type
                        type = TexturePalette.GetType(VertexInfos[i, j].TextureId, VertexInfos[i + 1, j].TextureId,
                                                      VertexInfos[i, j + 1].TextureId, VertexInfos[i + 1, j + 1].TextureId, TexturePaletteIndex);
                        if (type == 0)
                            continue;

                        sourceRect = palettes[TexturePaletteIndex].GetRectangle(type);

                        spriteBatch.Draw(palettes[TexturePaletteIndex].Texture as Texture2D, destinationRect, sourceRect, Color.White);

                        //  break;
                    }
                }
            spriteBatch.End();
            GraphicsDevice.DepthStencilBuffer = prevDepth;

            GraphicsDevice.SetRenderTarget(0, null);

            return TargetSurface.GetTexture();
        }

        public void Draw(Matrix world, Matrix view, Matrix projection)
        {
            BasicEffect effect = this._BasicEffect;

            // lights
            effect.DiffuseColor = new Vector3(0.5f, 0.5f, 0.5f);
            effect.SpecularColor = new Vector3(0.5f, 0.5f, 0.5f);
            effect.AmbientLightColor = new Vector3(0.8f, 0.8f, 0.8f);

            // coordinates
            effect.World = world;
            effect.View = view;
            effect.Projection = projection;

            effect.TextureEnabled = true;

            RenderStateManager.Instance.SetRenderStates(_RenderStates);

            // Set the graphics device to use our vertex declaration, vertex buffer, and index buffer.
            effect.GraphicsDevice.VertexDeclaration = this._VertexDeclaration;
            effect.GraphicsDevice.Indices = this._IndexBuffer;

            effect.GraphicsDevice.Vertices[0].SetSource(this._VertexBuffer, 0,
                                     VertexPositionNormalTangentBinormalTexture.SizeInBytes);
            // Begin the effect, and loop over all the effect passes.
            effect.Begin();

            foreach (EffectPass pass in effect.CurrentTechnique.Passes)
            {
                pass.Begin();

                // Draw the geometry.
                effect.GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList,
                                             0, 0, (_Resolution + 1) * (_Resolution + 1),
                                             0, _IndexBuffer.SizeInBytes / 12);

                pass.End();
            }

            effect.End();
        }
    }
}
